#include <time.h>
#include <mpi.h>

#include <cmath>
#include "GRASP.h"
#include "Kruskal.h"
#include "FileSTS.h"

#define MAX_ITER 120
//#define MACRO_ITER 20
//#define INTERNAL_MACRO_ITER (MAX_ITER/MACRO_ITER)

#define TAG_CONS_SOL 98

#define TAG_ILS_SOL 100

#define TAG_NEW_MACRO_ITER 99
#define TAG_CONTINUE_MACRO_ITER 101

#define RECV_MASTER_SOL 102
//#define RECV_CONS_AVAL 103

#define TAG_FINALIZE 104

double startwtime = 0.0, endwtime;
int melhorIter = 0;
int melhorIterCons = 0;
bool globalFinish = false;
SolucaoGRASPSTS *sbestGlobal = NULL;
SolucaoGRASPSTS *sbestMacroIter = NULL;
SolucaoGRASPSTS *sbestCons = NULL;
int masterCurrentMacroIter = 0;
int MACRO_ITER;
int INTERNAL_MACRO_ITER;
int* slaveMacroIter;

struct SolucaoStruct {
    double aval;
    char data[1000];
};

MPI_Datatype SolucaoType;
MPI_Datatype type[2] = {MPI_DOUBLE, MPI_CHAR};

void updateSolutions(SolucaoStruct &ss, int iter, bool updateLocal, bool updateCons) {

    //Atualiza a melhor global
    if (sbestGlobal == NULL) {
        sbestGlobal = new SolucaoGRASPSTS(ss.data);
        sbestGlobal->avaliacao = ss.aval;
        melhorIter = iter;
    } else {
        if (ss.aval < sbestGlobal->avaliacao) {
            delete sbestGlobal;
            sbestGlobal = new SolucaoGRASPSTS(ss.data);
            sbestGlobal->avaliacao = ss.aval;
            melhorIter = iter;
            //printf("CONS %s\n", ss.data);
            //printf("CONS %f\n", ss.aval);
        }
    }

    if (updateLocal) {
        //Atualiza a melhor da macro iter
        if (sbestMacroIter == NULL) {
            sbestMacroIter = new SolucaoGRASPSTS(ss.data);
            sbestMacroIter->avaliacao = ss.aval;
        } else {
            if (ss.aval < sbestMacroIter->avaliacao) {
                delete sbestMacroIter;
                sbestMacroIter = new SolucaoGRASPSTS(ss.data);
                sbestMacroIter->avaliacao = ss.aval;
                //printf("CONS %s\n", ss.data);
                //printf("CONS %f\n", ss.aval);
            }
        }
    }

    if (updateCons) {
        //Atualiza a melhor da macro iter
        if (sbestCons == NULL) {
            sbestCons = new SolucaoGRASPSTS(ss.data);
            sbestCons->avaliacao = ss.aval;
            melhorIterCons = iter;
        } else {
            if (ss.aval < sbestCons->avaliacao) {
                delete sbestCons;
                sbestCons = new SolucaoGRASPSTS(ss.data);
                sbestCons->avaliacao = ss.aval;
                melhorIterCons = iter;
            }
        }
    }
}

int main(int argc, char** argv) {
    int rank, size;
    if (argc == 4) {


        MPI_Init(&argc, &argv);
        MPI_Comm_size(MPI_COMM_WORLD, &size);
        MPI_Comm_rank(MPI_COMM_WORLD, &rank);

        MACRO_ITER = ceil(MAX_ITER / (3.0 * size));
        INTERNAL_MACRO_ITER = (MAX_ITER / MACRO_ITER);

        if ((INTERNAL_MACRO_ITER - (size - 2)) < 0) {
            printf("Numero de processadores invalidos\n");
            printf("O menor numero de processadores para MAX_ITER = %d e MACRO_ITER = %d é %d\n", MAX_ITER, MACRO_ITER, INTERNAL_MACRO_ITER + 1);
            exit(1);
        }

        srand(time(NULL));
        slaveMacroIter = new int[size];

        //Inicializando os dados de arquivo
        Solucao::loadEntrada(argv[1]);
        double alpha = atof(argv[2]);
        GRASP g(Solucao::tamanho, alpha);

        int blocken[2] = {1, 1000};
        MPI_Aint disp[2] = {0, sizeof (double)};

        //Definindo o datatype.
        MPI_Type_struct(2, blocken, disp, type, &SolucaoType);
        MPI_Type_commit(&SolucaoType);

        //Pegando o tempo inicial
        if (rank == 0)
            startwtime = MPI_Wtime();


        char *data = NULL;
        double aval;

        //Processamento dos escravos.
        if (rank != 0) {
            bool debutSlave = false;
            MPI_Status status;
            SolucaoStruct sr;
            //Recebendo a melhor solucao do servidor. 
            int contTotalIter = 0;

            //printf(">>>>> PROCESSO %d INICIOU \n", rank);

            while (true) {

                //Fase de Construcao e Busca Local.

                if (debutSlave)
                    printf("%d - Iniciou nova construcao\n", rank);
                SolucaoGRASPSTS *sol = g.construcao();
                SolucaoGRASPSTS *sbl = g.descida(sol);
                delete sol;

                //data = sbl->print();
                SolucaoStruct ss;
                strcpy(ss.data, sbl->print());
                ss.aval = sbl->avaliacao;
                //Enviando a sua melhor solucao para o servidor.
                MPI_Send(&ss, 1, SolucaoType, 0, TAG_CONS_SOL, MPI_COMM_WORLD);

                SolucaoStruct sr;
                //Recebendo a melhor solucao do servidor. 
                MPI_Recv(&sr, 1, SolucaoType, 0, RECV_MASTER_SOL, MPI_COMM_WORLD, &status);


                //while (true) { //Interno da MACRO_ITER

                if (debutSlave)
                    printf("%d - Iniciou nova iteracao interna\n", rank);
                contTotalIter++;


                delete sbl;
                sbl = new SolucaoGRASPSTS(sr.data);
                sbl->avaliacao = sr.aval;

                //Executando o VNS.
                SolucaoGRASPSTS *sils = g.ILS(sbl);

                SolucaoStruct ss2;
                strcpy(ss2.data, sils->print());
                ss2.aval = sils->avaliacao;
                //Enviando a sua melhor solucao para o servidor.
                MPI_Send(&ss2, 1, SolucaoType, 0, TAG_ILS_SOL, MPI_COMM_WORLD);
                delete sils;


                int finish;
                MPI_Recv(&finish, 1, MPI_INT, 0, TAG_FINALIZE, MPI_COMM_WORLD, &status);

                if (finish == 1) {
                    if(debutSlave)
                    printf("Rank[%d] Recebeu mensagem para finalizar", rank);
                    goto finalizeSlave;
                }

                //MPI_Recv(&sr, 1, SolucaoType, 0, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
                //
                //                    switch (status.MPI_TAG) {
                //                        case RECV_MASTER_SOL:
                //                        {
                //                            if(debutSlave)
                //                                printf("%d - Comando para continuar no ILS\n", rank);
                //                            continue;
                //                        }
                //                        case TAG_NEW_MACRO_ITER:
                //                        {
                //                            if(debutSlave)
                //                                printf("%d - Comando para fazer nova MACRO\n", rank);
                //                            goto newMacro;
                //                        }
                //                        case TAG_FINALIZE:
                //                        {
                //                            if(debutSlave)
                //                                printf("%d - Comando para finalizar tudo\n", rank);
                //                            goto finalizeSlave;
                //                        }
                //                    }

                //                }


            }

finalizeSlave:

            printf(">>>>> PROCESSO %d Finalizou e fez %d iteracoes \n", rank, contTotalIter);
        } else { //Processamento do Mestre
            bool debugMaster = false;
            int contTotalIter = 0;
            int contMacroIter = 0;
            int contInternalIter = 0;
            int cont_process_dead = 0;

            for (int i = 0; i < size; i++) {
                slaveMacroIter[i] = 0;
            }

            //            if(debugMaster){
            printf("[Mestre] INTERNAL_MACRO_ITER = %d\n", INTERNAL_MACRO_ITER);
            printf("[Mestre] MACRO_ITER = %d\n", MACRO_ITER);
            //          }

            while (true) {
                MPI_Status status;
                SolucaoStruct ss;
                int source;

                MPI_Recv(&ss, 1, SolucaoType, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD,
                        &status);

                //               if (debugMaster)
                //                    printf("[Mestre] contTotalIter = %d | contMacroIter = %d | contInternalIter = %d\n", contTotalIter, contMacroIter, contInternalIter);

                bool updateCons = true;
                bool updateMacroIter = (slaveMacroIter[status.MPI_SOURCE] == masterCurrentMacroIter); //So se for um cara da iteração corrente

                switch (status.MPI_TAG) {
                    case TAG_CONS_SOL:

                        //printf("CONSTRUCAO Iteracao corrente %d  recebida de %d\n", cont, source);

                        source = status.MPI_SOURCE;

                        updateSolutions(ss, contTotalIter, updateMacroIter, updateCons);

                        SolucaoStruct sr;
                        if (!updateMacroIter) {
                            slaveMacroIter[status.MPI_SOURCE] = masterCurrentMacroIter; //Atualiza a macro iter.
                            
                            strcpy(sr.data, sbestCons->print());
                            sr.aval = sbestCons->avaliacao;
                        } else {
                            
                            strcpy(sr.data, sbestMacroIter->print());
                            sr.aval = sbestMacroIter->avaliacao;
                        }

                       


                        //printf("ANTES CONSTRUCAO Iteracao corrente %d  recebida de %d\n", cont, source);
                        MPI_Send(&sr, 1, SolucaoType, source, RECV_MASTER_SOL,
                                MPI_COMM_WORLD);
                        //	printf("DEPOIS CONSTRUCAO Iteracao corrente %d  recebida de %d\n", cont, source);
                        break;
                    case TAG_ILS_SOL:

                        updateCons = false;
                        updateSolutions(ss, contTotalIter, updateMacroIter, updateCons);

                        contTotalIter++;

                        if (slaveMacroIter[status.MPI_SOURCE] == masterCurrentMacroIter) {
                            contInternalIter++;
                        }

                        if (contTotalIter == (MAX_ITER - (size - 2))) {
                            globalFinish = true;
                        }

                        //Mudança da MACRO_ITER
                        if (contInternalIter == (INTERNAL_MACRO_ITER - (size - 2))) {
                            masterCurrentMacroIter++;
                            //slaveMacroIter[status.MPI_SOURCE] = masterCurrentMacroIter;
                            contMacroIter++;
                            contInternalIter = 0;
                            sbestMacroIter = NULL;
                        }




                        if (globalFinish) {

                            //				printf("[Mestre] enviando comando para finalizar para %d\n", status.MPI_SOURCE);
                            int finish = 1;
                            MPI_Send(&finish, 1, MPI_INT, status.MPI_SOURCE, TAG_FINALIZE,
                                    MPI_COMM_WORLD);

                            cont_process_dead++;
                            //				printf("[Mestre] Numero de processos mortos %d\n", cont_process_dead);	
                            if (cont_process_dead == (size - 1)) {
                                goto process_finish;
                            }
                        }
                        else {


                            int finish = 0;

                            MPI_Send(&finish, 1, MPI_INT, status.MPI_SOURCE, TAG_FINALIZE,
                                    MPI_COMM_WORLD);
                        }





                        break;
                    default:
                        puts("Erro no sincronismo das mensagens.");
                        exit(0);
                        break;
                }


                /*                if (globalFinish) {
                                    cont_process_dead++;
                                    printf("[Mestre] Processos Mortos %d\n", cont_process_dead);
                                    if (cont_process_dead == (size - 1)) {
                                        printf("[Mestre] executou o braek do while principal\n");
                                        break;
                                    }
                                }
                 */

            }
        }

process_finish:
        if (rank == 0) {
            endwtime = MPI_Wtime();
            FileSTS::printInstancia(argv[3], size, alpha, sbestGlobal->tamanho, sbestGlobal->numClient, melhorIter,
                    sbestGlobal->avaliacao, (int) ((endwtime - startwtime) * 1000));
            printf("Final: %s F: %f Tempo: %f Iteração: %d\n", sbestGlobal->print(), sbestGlobal->avaliacao,
                    endwtime - startwtime, melhorIter);
        }

    } else {
        puts("<usage: grasp nameFile alpha estrategia>");
    }


    //printf("Processo %d chamou o MPI_Finalize()\n", rank);    
    MPI_Finalize();
    return 0;
}


/**
 * 2 - 19
 * 3 - 38
 * 4 - 57
 */
